a tool for shared writing and social publishing
1"use client";
2import { ShortcutKey } from "../../../components/Layout";
3import { Media } from "../../../components/Media";
4import { Popover } from "../../../components/Popover";
5import { metaKey } from "src/utils/metaKey";
6import { useEntitySetContext } from "../../../components/EntitySetProvider";
7import { useState } from "react";
8import { ActionButton } from "components/ActionBar/ActionButton";
9import { HelpSmall } from "../../../components/Icons/HelpSmall";
10import { isMac } from "src/utils/isDevice";
11import { useIsMobile } from "src/hooks/isMobile";
12
13export const HelpButton = (props: { noShortcuts?: boolean }) => {
14 let entity_set = useEntitySetContext();
15 let isMobile = useIsMobile();
16
17 return entity_set.permissions.write ? (
18 <Popover
19 side={isMobile ? "top" : "right"}
20 align={isMobile ? "center" : "start"}
21 asChild
22 className="max-w-xs w-full p-0!"
23 trigger={<ActionButton icon={<HelpSmall />} label="About" />}
24 >
25 <div
26 className={`flex flex-col text-sm gap-2 p-3 text-secondary max-h-[70vh] overflow-y-auto p-2" : ""}`}
27 >
28 {/* about links */}
29 <HelpLink text="📖 Leaflet Manual" url="https://about.leaflet.pub" />
30 <HelpLink text="💡 Make with Leaflet" url="https://make.leaflet.pub" />
31 <HelpLink
32 text="✨ Explore Publications"
33 url="https://leaflet.pub/discover"
34 />
35 <HelpLink text="📣 Newsletter" url="https://buttondown.com/leaflet" />
36 {/* contact links */}
37 <div className="columns-2 gap-2">
38 <HelpLink
39 text="🦋 Bluesky"
40 url="https://bsky.app/profile/leaflet.pub"
41 />
42 <HelpLink text="💌 Email" url="mailto:contact@leaflet.pub" />
43 </div>
44 {/* keyboard shortcuts: desktop only */}
45 <Media mobile={false}>
46 {!props.noShortcuts && (
47 <>
48 <hr className="text-border my-1" />
49 <div className="flex flex-col gap-1">
50 <Label>Text Shortcuts</Label>
51 <KeyboardShortcut name="Bold" keys={[metaKey(), "B"]} />
52 <KeyboardShortcut name="Italic" keys={[metaKey(), "I"]} />
53 <KeyboardShortcut name="Underline" keys={[metaKey(), "U"]} />
54 <KeyboardShortcut
55 name="Highlight"
56 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "H"]}
57 />
58 <KeyboardShortcut
59 name="Strikethrough"
60 keys={[metaKey(), isMac() ? "Ctrl" : "Meta", "X"]}
61 />
62 <KeyboardShortcut name="Inline Link" keys={[metaKey(), "K"]} />
63 <KeyboardShortcut
64 name="Make Title"
65 keys={[metaKey(), isMac() ? "Opt" : "Alt", "1"]}
66 />
67 <KeyboardShortcut
68 name="Make Heading"
69 keys={[metaKey(), isMac() ? "Opt" : "Alt", "2"]}
70 />
71 <KeyboardShortcut
72 name="Make Subheading"
73 keys={[metaKey(), isMac() ? "Opt" : "Alt", "3"]}
74 />
75 <KeyboardShortcut
76 name="Regular Text"
77 keys={[metaKey(), isMac() ? "Opt" : "Alt", "0"]}
78 />
79 <KeyboardShortcut
80 name="Large Text"
81 keys={[metaKey(), isMac() ? "Opt" : "Alt", "+"]}
82 />
83 <KeyboardShortcut
84 name="Small Text"
85 keys={[metaKey(), isMac() ? "Opt" : "Alt", "-"]}
86 />
87
88 <Label>Block Shortcuts</Label>
89 {/* shift + up/down arrows (or click + drag): select multiple blocks */}
90 <KeyboardShortcut
91 name="Move Block Up"
92 keys={["Shift", metaKey(), "↑"]}
93 />
94 <KeyboardShortcut
95 name="Move Block Down"
96 keys={["Shift", metaKey(), "↓"]}
97 />
98 {/* cmd/ctrl-a: first selects all text in a block; again selects all blocks on page */}
99 {/* cmd/ctrl + up/down arrows: go to beginning / end of doc */}
100
101 <Label>Canvas Shortcuts</Label>
102 <OtherShortcut name="Add Block" description="Double click" />
103 <OtherShortcut name="Select Block" description="Long press" />
104
105 <Label>Outliner Shortcuts</Label>
106 <KeyboardShortcut
107 name="Make List"
108 keys={[metaKey(), isMac() ? "Opt" : "Alt", "L"]}
109 />
110 {/* tab / shift + tab: indent / outdent */}
111 <KeyboardShortcut
112 name="Toggle Checkbox"
113 keys={[metaKey(), "Enter"]}
114 />
115 <KeyboardShortcut
116 name="Toggle Fold"
117 keys={[metaKey(), "Shift", "Enter"]}
118 />
119 <KeyboardShortcut
120 name="Fold All"
121 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↑"]}
122 />
123 <KeyboardShortcut
124 name="Unfold All"
125 keys={[metaKey(), isMac() ? "Opt" : "Alt", "Shift", "↓"]}
126 />
127 </div>
128 </>
129 )}
130 </Media>
131 {/* links: terms and privacy */}
132 <hr className="text-border my-1" />
133 {/* <HelpLink
134 text="Terms and Privacy Policy"
135 url="https://leaflet.pub/legal"
136 /> */}
137 <div>
138 <a href="https://leaflet.pub/legal" target="_blank">
139 Terms and Privacy Policy
140 </a>
141 </div>
142 </div>
143 </Popover>
144 ) : null;
145};
146
147const KeyboardShortcut = (props: { name: string; keys: string[] }) => {
148 return (
149 <div className="flex gap-2 justify-between items-center">
150 {props.name}
151 <div className="flex gap-1 items-center font-bold">
152 {props.keys.map((key, index) => {
153 return <ShortcutKey key={index}>{key}</ShortcutKey>;
154 })}
155 </div>
156 </div>
157 );
158};
159
160const OtherShortcut = (props: { name: string; description: string }) => {
161 return (
162 <div className="flex justify-between items-center">
163 <span>{props.name}</span>
164 <span>
165 <strong>{props.description}</strong>
166 </span>
167 </div>
168 );
169};
170
171const Label = (props: { children: React.ReactNode }) => {
172 return <div className="text-tertiary font-bold pt-2 ">{props.children}</div>;
173};
174
175const HelpLink = (props: { url: string; text: string }) => {
176 const [isHovered, setIsHovered] = useState(false);
177 const handleMouseEnter = () => {
178 setIsHovered(true);
179 };
180 const handleMouseLeave = () => {
181 setIsHovered(false);
182 };
183 return (
184 <a
185 href={props.url}
186 target="_blank"
187 className="py-2 px-2 rounded-md flex flex-col gap-1 bg-border-light hover:bg-border hover:no-underline"
188 style={{
189 backgroundColor: isHovered
190 ? "rgb(var(--accent-light))"
191 : "color-mix(in oklab, rgb(var(--accent-contrast)), rgb(var(--bg-page)) 75%)",
192 }}
193 onMouseEnter={handleMouseEnter}
194 onMouseLeave={handleMouseLeave}
195 >
196 <strong>{props.text}</strong>
197 </a>
198 );
199};